package com.limetalks;

/*
 * RED5 Open Source Flash Server - http://www.osflash.org/red5
 * 
 * Copyright (c) 2006-2008 by respective authors (see below). All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or modify it under the 
 * terms of the GNU Lesser General Public License as published by the Free Software 
 * Foundation; either version 2.1 of the License, or (at your option) any later 
 * version. 
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along 
 * with this library; if not, write to the Free Software Foundation, Inc., 
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 */

import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.adapter.MultiThreadedApplicationAdapter;
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;
import org.red5.server.api.Red5;
import org.red5.server.api.event.IEvent;
import org.red5.server.api.event.IEventListener;
import org.red5.server.api.service.ServiceUtils;

import com.limetalks.dao.StreamDAO;
import com.limetalks.dao.UserDAO;
import com.limetalks.data.Stream;
import com.limetalks.data.User;
import com.limetalks.util.GlobalStrings;
import com.limetalks.util.SessionTracker;

/**
 * Sample application that uses the client manager.
 * 
 * @author The Red5 Project (red5@osflash.org)
 */
public class ApplicationChat extends MultiThreadedApplicationAdapter implements IEventListener{
	private final static String ON_STREAM_STARTED_CLIENT = "onStreamStarted_Client";
	private final static String ON_STREAM_STOPPED_CLIENT = "onStreamStopped_Client";
	private final static String ON_STREAM_JOIN_CLIENT = "onStreamJoin_Client";
	private final static String ON_STREAM_LEAVE_CLIENT = "onStreamLeave_Client";
	private final static String ON_USER_LOGOUT_CLIENT = "onUserLogout_Client";
	
	private IScope videoScope;
	private IScope sphereScope;
	/*private EntityManagerFactory emf;
	private UserDAO userDAO;
	private StreamDAO streamDAO;
	private Set<Stream> streams = new HashSet<Stream>();
	private Set<User> users = new HashSet<User>();
	private Map<IClient, User> clients = new HashMap<IClient, User>();
	SessionTracker sessionTracker = SessionTracker.instance();*/

	public ApplicationChat() {

		super();
		/*if (emf == null) {
			emf = Persistence.createEntityManagerFactory("LimeTalksUnit");
			userDAO = new UserDAO(emf);
			streamDAO = new StreamDAO(emf);
		}*/
	}

	@Override
	public boolean appStart(IScope app) {
		System.err.println("appStart contextPath=" + app.getContextPath()
				+ "; name=" + app.getName());
		 // setup aphere scope
	    if (!app.createChildScope(GlobalStrings.SPHERE_SCOPE_NAME))
	        return false;
	    sphereScope = app.getScope(GlobalStrings.SPHERE_SCOPE_NAME);
	    sphereScope.addEventListener(this);

	    // setup video scope
	    if (!app.createChildScope(GlobalStrings.VIDEO_SCOPE_NAME))
	        return false;
	    videoScope = app.getScope(GlobalStrings.VIDEO_SCOPE_NAME);
	    videoScope.addEventListener(this);
		return true;
	}

	@Override
	public boolean appConnect(IConnection conn, Object[] params) {
		System.err.println("appConnect conn.host=" + conn.getHost()
				+ "; conn.path=" + conn.getPath());
		return super.appConnect(conn, params);
	}

	/** {@inheritDoc} */
	@Override
	public boolean connect(IConnection conn, IScope scope, Object[] params) {
		if (!super.connect(conn, scope, params))
	        return false;
	    System.out.println("Client '" + conn.getClient().getId() + "' connecting to: " + scope.getPath() + "/" + scope.getName() +
	        " (" + scope.getContextPath() + ")");
	    /*System.out.println("connect() starting work " + (new Date()).toString());
	    try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	    System.out.println("connect() finishing work " + (new Date()).toString());*/
	    return true;
	}

	/** {@inheritDoc} */
	@Override
	public void disconnect(IConnection conn, IScope scope) {
		System.err.println("Client '" + conn.getClient().getId() + "' disconnecting from conn.host=" + conn.getHost()
				+ "; conn.path=" + conn.getPath() + "; scope.contextPath="
				+ scope.getContextPath() + ";");
		super.disconnect(conn, scope);
	}

	@Override
	public synchronized boolean start(IScope scope) {
		System.err.println("start contextPath=" + scope.getContextPath()
				+ "; name=" + scope.getName());
		return super.start(scope);
	}

	@Override
	public boolean roomStart(IScope roomScope) {
		System.err.println("roomStart contextPath="
				+ roomScope.getContextPath() + "; name=" + roomScope.getName());
		ServiceUtils.invokeOnAllConnections(videoScope, ON_STREAM_STARTED_CLIENT,
				new Object[] { roomScope.getName() });
		return super.roomStart(roomScope);
	}

	@Override
	public synchronized void stop(IScope scope) {
		System.err.println("stop contextPath=" + scope.getContextPath()
				+ "; name=" + scope.getName());
		super.stop(scope);
	}

	@Override
	public void roomStop(IScope roomScope) {
		System.err.println("roomStop contextPath=" + roomScope.getContextPath()
				+ "; name=" + roomScope.getName());
		ServiceUtils.invokeOnAllConnections(videoScope, ON_STREAM_STOPPED_CLIENT,
				new Object[] { roomScope.getName() });
		super.roomStop(roomScope);
	}

	@Override
	public synchronized boolean join(IClient client, IScope scope) {
		// System.err.println("join client.id=" + client.getId() +
		// " scope.contextPath=" + scope.getContextPath() + "; scope.name=" +
		// scope.getName());
		return super.join(client, scope);
	}

	@Override
	public boolean roomJoin(IClient client, IScope scope) {
		System.err.println("roomJoin client.id=" + client.getId()
				+ " scope.contextPath=" + scope.getContextPath()
				+ "; scope.name=" + scope.getName());
		boolean result = super.roomJoin(client, scope);

		if (result) {
			/*ServiceUtils.invokeOnAllConnections(scope,
					"onStreamUpdated_Client", new Object[] { stream });*/
			ServiceUtils.invokeOnAllConnections(scope, ON_STREAM_JOIN_CLIENT,
					new Object[] { client.getId(), scope.getName() });
		}
		return result;
	}

	@Override
	public synchronized void leave(IClient client, IScope scope) {
		System.err.println("leave client.id=" + client.getId()
				+ " scope.contextPath=" + scope.getContextPath()
				+ "; scope.name=" + scope.getName());
		super.leave(client, scope);
	}

	
	
	@Override
	public void roomLeave(IClient client, IScope scope) {
		System.err.println("roomLeave client.id=" + client.getId()
				+ " master=" + isMaster(client) + " scope.contextPath=" + scope.getContextPath()
				+ "; scope.name=" + scope.getName() + " stream=" + isStream(scope));
		ServiceUtils.invokeOnAllConnections(scope, ON_STREAM_LEAVE_CLIENT,
				new Object[] { client.getId(), scope.getName() });		
		if(isStream(scope)){
			if(isMaster(client)){
//				ServiceUtils.invokeOnAllConnections(videoScope, ON_STREAM_STOPPED_CLIENT, new Object[]{scope.getName()});				
				for (IClient guest : scope.getClients()) {
					System.err.println("\t guest.id=" + guest.getId());
					ServiceUtils.invokeOnClient(guest, scope, ON_STREAM_STOPPED_CLIENT, new Object[]{scope.getName()});
					guest.disconnect();
				}
//				videoScope.removeChildScope(scope);
			}
		}
		super.roomLeave(client, scope);
	}

	public void login_Server(IConnection conn, String sessionId) {
		System.err.println("login_Server sessionId: " + sessionId);
	}

	public void logout_Server() {
		IConnection conn = Red5.getConnectionLocal();
		IClient client = conn.getClient();
		ServiceUtils.invokeOnAllConnections(conn.getScope(),
				ON_USER_LOGOUT_CLIENT, new Object[] {client.getId()});
	}

	public boolean startAndJoinStream_Server(String streamId) {
		IScope scope = getChildScope(streamId.toString());
		if (scope == null) {
			if (!startStream_Server(streamId)) {
				return false;
			}
		}
		return joinStream_Server(streamId);
	}

	public boolean startStream_Server(String streamId) {
		boolean result = createChildScope(streamId.toString());
		return result;
	}

	public void stopStream_Server(Integer streamId) {
		IScope roomScope = getChildScope(streamId.toString());
		removeChildScope(roomScope);
	}

	public boolean joinStream_Server(String streamId) {
		IConnection conn = Red5.getConnectionLocal();
		IClient client = conn.getClient();
		IScope currentScope = conn.getScope();
		IScope streamScope = currentScope.getScope(streamId.toString());
		if(streamScope == null){
			if(currentScope.createChildScope(streamId)){
				streamScope = currentScope.getScope(streamId.toString());
			}
		}
		// getScope().disconnect(conn); // leave root
		// return conn.connect(roomScope); // connect to the stream
		if(streamScope != null){
//			disconnect(conn, currentScope);
			connect(conn, streamScope, new Object[0]);
			return join(client, streamScope);
		}else{
			return false;
		}
	}

	public void leaveStream_Server() {
		IConnection conn = Red5.getConnectionLocal();
		IClient client = conn.getClient();
		IScope currentScope = conn.getScope();
//		disconnect(conn, currentScope);
		leave(client, currentScope);
//		connect(conn, videoScope, new Object[0]);
//		super.leave(client, currentScope);
	}

	@Override
	public void notifyEvent(IEvent arg0) {
		System.err.println("notifyEvent");
	}
	
	private boolean isStream(IScope scope) {
		return scope.getName().matches(GlobalStrings.STARTS_WITH_DIGIT);
	}
	
	private boolean isMaster(IClient client) {
		return Integer.parseInt(client.getId()) % 2 == 0;
	}
}
